package cz.proks.schoolpupilbook.gae.dao;

import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.persistence.EntityManager;
import javax.persistence.NoResultException;

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;

import cz.proks.schoolpupilbook.gae.converter.Converter;
import cz.proks.schoolpupilbook.gae.dao.PersistenceManager.PersistenceExecutor;
import cz.proks.schoolpupilbook.gae.dbo.IsIndexableDBO;
import cz.proks.schoolpupilbook.shared.domain.IsIndexableDomain;
import cz.proks.schoolpupilbook.shared.util.Tester;

public abstract class BaseDAO<T extends IsIndexableDBO, C extends IsIndexableDomain> {
	private final static Logger logger = Logger.getLogger(BaseDAO.class.getName());
				
	protected abstract Class<T> getDBOClass();	
	protected abstract Converter<T,C> getConverter();
	
	public C find(final Object id) {
		if (id == null)
			return null;
		
		Converter<T,C> converter = getConverter();
		
		T dbo = findDBO(id);
		C domain = converter.getDomain(dbo);
		return domain;
	}
	
	public void save(C domain) {
		Converter<T,C> converter = getConverter();
		T dbo = converter.getDBO(domain);
		
		if (dbo != null) {			
			saveDBO(dbo);		
			domain.setId(KeyFactory.keyToString(dbo.getId()));
		}
	}
	
	public void remove(C domain) {
		Converter<T,C> converter = getConverter();
		T dbo = converter.getDBO(domain);
		removeDBO(dbo);
	}
	
	public List<C> list() {
		List<T> dboList = listDBO();
		List<C> domainList = new LinkedList<C>();
		Converter<T,C> converter = getConverter();
		
		if (dboList != null)
			for (T dbo : dboList) {
				domainList.add(converter.getDomain(dbo));
			}
		
		return domainList;
	}
	
	protected T findDBO(final Object id) {
		return getAnyDBO(getDBOClass(), id);
	}
	
	protected void saveDBO(final T obj) {
		saveAnyDBO(obj);
	}
	
	protected void removeDBO(final T obj) {
		removeAnyDBO(obj);
	}
	
	protected List<T> listDBO() {
		return listAnyDBO(getDBOClass());
	}	
	
	protected static <X extends IsIndexableDBO> X getAnyDBO(final Class<X> dboClass, final Object id) {
		logger.log(Level.FINE, "getAnyDBO():: id=" + id);
		
		if (dboClass == null || id == null)
			return null;
		
		return PersistenceManager.execute(new PersistenceExecutor<X>() {
			@Override
			public X doExecute(EntityManager em) {
				try {					
					return em.find(dboClass, id instanceof Key ? id : 
											 id instanceof String ? KeyFactory.stringToKey(id.toString()) : id);
				} catch (NoResultException e) {
					return null;
				}
			}
		}, false);
	}
	
	protected static <X extends IsIndexableDBO> void saveAnyDBO(final X obj) {
		logger.log(Level.FINE, "saveAnyDBO():: id=" + obj == null ? ">> 'obj == null'" : Tester.isEmpty(obj.getId()) ? "NULL" : KeyFactory.keyToString(obj.getId()));
		
		if (obj == null)
			return;
		
		PersistenceManager.execute(new PersistenceExecutor<Void>() {
			@Override
			public Void doExecute(EntityManager em) {
				if (Tester.isEmpty(obj.getId()))
					em.persist(obj);
				else 
					em.merge(obj);
				
				return null;
			}
		}, true);		
	}
	
	protected static <X extends IsIndexableDBO> void removeAnyDBO(final X obj) {
		logger.log(Level.FINE, "removeAnyDBO():: id=" + obj == null ? ">> 'obj == null'" : Tester.isEmpty(obj.getId()) ? "NULL" : KeyFactory.keyToString(obj.getId()));
		
		if (obj == null)
			return;
		
		PersistenceManager.execute(new PersistenceExecutor<Void>() {
			@Override
			public Void doExecute(EntityManager em) {
				em.remove(obj);
				return null;
			}
		}, true);		
	}
	
	@SuppressWarnings("unchecked")
	protected static <X extends IsIndexableDBO> List<X> listAnyDBO(final Class<X> dboClass) {
		logger.log(Level.FINE, "listAnyDBO()");
		final LinkedList<X> dboList = new LinkedList<X>();
		
		if (dboClass == null)
			throw new RuntimeException("Class<X> dboClass cannot be NULL!");
		
		return PersistenceManager.execute(new PersistenceExecutor<List<X>>() {
			@Override
			public List<X> doExecute(EntityManager em) {
				List<X> jpaList = null;
				try {
					jpaList = em.createQuery("SELECT FROM " + dboClass.getName())
							 		 .getResultList();					
				} catch (NoResultException e) {};
				
				if (jpaList != null)
					for (X dbo : jpaList)
						dboList.add(dbo);
				
				return dboList;
			}
		}, false);
	}
}
